﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.Xna.Framework;

namespace UFODefense
{
    //Singleton class for detecting UFO collisions with lasers and missiles
    class UFOCollisionObserver
    {
        private static UFOCollisionObserver instance;

        public static UFOCollisionObserver Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new UFOCollisionObserver();
                }
                return instance;
            }
        }

        List<UFO> m_observers;

        /// <summary>
        /// Used for attaching UFOs to the observation list
        /// </summary>
        /// <param name="ufo">The attached UFO.</param>
        public void AttachUFO(UFO ufo)
        {
            m_observers.Add(ufo);
        }

        private UFOCollisionObserver()
        {
            m_observers = new List<UFO>();

        }
        /// <summary>
        /// Overloaded Notify-method, used by the Laser class
        /// </summary>
        /// <param name="lineStart">The start of the laser<./param>
        /// <param name="lineEnd">The end of the laser.</param>
        public void Notify(Vector2 lineStart, Vector2 lineEnd)
        {
            for (int i = (m_observers.Count - 1); i >= 0; --i)
            {
                if (LineIntersectRectangle(lineStart, lineEnd, m_observers[i].location))
                {
                    //call the destroy method for the UFO and remove from observables
                    m_observers[i].Destroy();
                    m_observers.RemoveAt(i);
                }

            }
        }
        /// <summary>
        /// Overloaded Notify-method, used by the Missile class
        /// </summary>
        /// <param name="rectangle">The missile rectangle</param>
        /// <returns>True if collision, false if not</returns>
        public bool Notify(Rectangle rectangle)
        {
            for (int i = (m_observers.Count - 1); i >= 0; --i)
            {
                if (rectangle.Intersects(m_observers[i].location))
                {
                    //call the destroy method for the UFO and remove from observables
                    m_observers[i].Destroy();
                    m_observers.RemoveAt(i);
                    return true;
                }

            }
            return false;
        }

        bool LineRectangleCollision(Vector2 lineStart, Vector2 lineEnd, Rectangle rectangle)
        {
            Vector2 topLeft = new Vector2(rectangle.X, rectangle.Y);
            Vector2 topRight = new Vector2((rectangle.X + rectangle.Width), rectangle.Y);
            Vector2 bottomLeft = new Vector2(rectangle.X, (rectangle.Y + rectangle.Height));
            Vector2 bottomRight = new Vector2((rectangle.X + rectangle.Width) + (rectangle.Y + rectangle.Height));
            //upper left corner <-> upper right corner
            if (LineLineCollision(lineStart, lineEnd, topLeft, topRight)) return true;
            //upper left corner <-> bottom left corner
            if (LineLineCollision(lineStart, lineEnd, topLeft, bottomLeft)) return true;
            //upper right corner <-> bottom right corner
            if (LineLineCollision(lineStart, lineEnd, topRight, bottomRight)) return true;
            //bottom left corner <-> bottom right corner
            if (LineLineCollision(lineStart, lineEnd, bottomLeft, bottomRight)) return true;

            //no matches
            return false;
        }


        //line-line collision detection.
        //http://stackoverflow.com/questions/3746274/line-intersection-with-aabb-rectangle
        bool LineLineCollision(Vector2 a1, Vector2 a2, Vector2 b1, Vector2 b2)
        {
            Vector2 intersection = Vector2.Zero;

            Vector2 b = a2 - a1;
            Vector2 d = b2 - b1;
            float bDotDPerp = b.X * d.Y - b.Y * d.X;

            // if b dot d == 0, it means the lines are parallel so have infinite intersection points
            if (bDotDPerp == 0)
                return false;

            Vector2 c = b1 - a1;
            float t = (c.X * d.Y - c.Y * d.X) / bDotDPerp;
            if (t < 0 || t > 1)
                return false;

            float u = (c.X * b.Y - c.Y * b.X) / bDotDPerp;
            if (u < 0 || u > 1)
                return false;

            intersection = a1 + t * b;

            return true;
        }

        bool LineIntersectRectangle(Vector2 lineStart, Vector2 lineEnd, Rectangle rectangle)
        {

            // Find min and max X for the segment

            float a_rectangleMinX = rectangle.X;
            float a_rectangleMinY = rectangle.Y;
            float a_rectangleMaxX = rectangle.X + rectangle.Width;
            float a_rectangleMaxY = rectangle.Y + rectangle.Height;
            float a_p1x = lineStart.X;
            float a_p1y = lineStart.Y;
            float a_p2x = lineEnd.X;
            float a_p2y = lineEnd.Y;

            float minX = a_p1x;
            float maxX = a_p2x;

            if (a_p1x > a_p2x)
            {
                minX = a_p2x;
                maxX = a_p1x;
            }

            // Find the intersection of the segment's and rectangle's x-projections

            if (maxX > a_rectangleMaxX)
            {
                maxX = a_rectangleMaxX;
            }

            if (minX < a_rectangleMinX)
            {
                minX = a_rectangleMinX;
            }

            if (minX > maxX) // If their projections do not intersect return false
            {
                return false;
            }

            // Find corresponding min and max Y for min and max X we found before

            float minY = a_p1y;
            float maxY = a_p2y;

            float dx = a_p2x - a_p1x;

            if (Math.Abs(dx) > 0.0000001)
            {
                float a = (a_p2y - a_p1y) / dx;
                float b = a_p1y - a * a_p1x;
                minY = a * minX + b;
                maxY = a * maxX + b;
            }

            if (minY > maxY)
            {
                float tmp = maxY;
                maxY = minY;
                minY = tmp;
            }

            // Find the intersection of the segment's and rectangle's y-projections

            if (maxY > a_rectangleMaxY)
            {
                maxY = a_rectangleMaxY;
            }

            if (minY < a_rectangleMinY)
            {
                minY = a_rectangleMinY;
            }

            if (minY > maxY) // If Y-projections do not intersect return false
            {

                return false;
            }

            return true;
        }

    }
}
